package org.tulkas.util;

import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

/**
 * Utility class to handle common java types, like array, String...
 * 
 * @author <a href="mailto:tulkas.web@gmail.com">Tulkas</a>
 *
 */
public abstract class LangUtil {
	/**
	 * Extend a array with a new element. Create a new array and fill the elements in.
	 * 
	 * @param <T>
	 * @param array
	 * @param element
	 * @return
	 */
	@SuppressWarnings( { "unchecked" })
	public static <T> T[] extendArray(T[] array, T element) {
		if (array == null)
			return null;
		T[] newArray = (T[]) Array.newInstance(array.getClass()
				.getComponentType(), array.length + 1);
		for (int i = 0; i < array.length; i++) {
			newArray[i] = array[i];
		}
		newArray[array.length] = element;
		return newArray;
	}

	/**
	 * remove the element in a specific index from an array and recreate the new array.
	 * 
	 * @param array
	 * @param index
	 * @return
	 */
	@SuppressWarnings( { "unchecked" })
	public static <T> T[] reduceArray(T[] array, int index) {
		if (array == null)
			return null;
		if (array.length == 0)
			return array;
		T[] newArray = (T[]) Array.newInstance(array.getClass()
				.getComponentType(), array.length - 1);
		int c = 0;
		for (int i = 0; i < array.length; i++) {
			newArray[c] = array[i];
			if (i != index)
				c++;
		}
		return newArray;
	}

	/**
	 * remove a specific element from an array and recreate the new array. <BR/>
	 * This method use '==' to locate element, not 'equals'
	 * 
	 * @param array
	 * @param element
	 * @return
	 */
	@SuppressWarnings( { "unchecked" })
	public static <T> T[] reduceArray(T[] array, T element) {
		if (array == null)
			return null;
		if (array.length == 0)
			return array;
		T[] newArray = (T[]) Array.newInstance(array.getClass()
				.getComponentType(), array.length - 1);
		int c = 0;
		for (int i = 0; i < array.length; i++) {
			newArray[c] = array[i];
			if (array[i] != element)
				c++;
		}
		return newArray;
	}

	/**
	 * Check if a object is null. If it is null, throw a NullPointerException with a specific message.
	 * 
	 * @param obj
	 * @param name
	 */
	public static void checkNotNull(Object obj, String name) {
		if (obj == null) {
			String text = "";
			if (name != null)
				text = "[" + name + "]";
			else
				text = "Object";
			throw new NullPointerException(text + " can not be null!");
		}
	}

	/**
	 * Convert objects in a Iterator to the comma-separated format. During converting, call the objects' toString() method.
	 * 
	 * @param it
	 * @return
	 */
	public static <T> String toCSV(Iterator<T> it) {
		StringBuffer sb = new StringBuffer();
		int i = 0;
		while (it.hasNext()) {
			Object value = it.next();
			if (value != null) {
				if (i > 0)
					sb.append(',');
				sb.append(value.toString());
			}
			i++;
		}
		return sb.toString();
	}

	/**
	 * Convert objects in an array to the comma-separated format. During converting, call the objects' toString() method.
	 * 
	 * @param array
	 * @return
	 */
	public static <T> String toCSV(T[] array) {
		StringBuffer sb = new StringBuffer();
		for (int j = 0; j < array.length; j++) {
			T value = array[j];
			if (value != null) {
				if (j > 0)
					sb.append(',');
				sb.append(value.toString());
			}
		}
		return sb.toString();
	}

	public static Object getAnyField(Object obj, String fieldName)
			throws Exception {
		Class<?> cls = obj.getClass();
		Field field = cls.getDeclaredField(fieldName);
		field.setAccessible(true);
		return field.get(obj);
	}

	public static Object invokeAnyMethod(Object obj, String methodName,
			Class<?>[] paramTypes, Object[] params) throws Exception {
		Class<?> cls = obj.getClass();
		Method method = cls.getDeclaredMethod(methodName, paramTypes);
		method.setAccessible(true);
		return method.invoke(obj, params);
	}

	/**
	 * For every properties in the <code>bean</code>, try to find the value in the <code>map</code>.
	 * If found, set the bean value. If not found, just do nothing.
	 * 
	 * @param map, key is the bean property name. value is the property value.
	 * @param bean
	 * @throws IntrospectionException 
	 * @throws InvocationTargetException 
	 * @throws IllegalAccessException 
	 * @throws  
	 */
	public static void mapToBean(Map<String, ?> map, Object bean)
			throws IntrospectionException, IllegalAccessException,
			InvocationTargetException {
		BeanInfo bi = Introspector.getBeanInfo(bean.getClass());
		PropertyDescriptor[] pds = bi.getPropertyDescriptors();
		for (PropertyDescriptor pd : pds) {
			Method m = pd.getWriteMethod();
			Object v = map.get(pd.getName());
			if (m != null && v != null) {
				m.invoke(bean, v);
			}
		}
	}

	/**
	 * For every properties in the <code>bean</code>, get the value and set it to the <code>map</code>.
	 * 
	 * @param bean, key is the bean property name. value is the property value.
	 * @param map
	 * @throws IntrospectionException 
	 */
	public static Map<String, Object> beanToMap(Object bean)
			throws IntrospectionException, IllegalAccessException,
			InvocationTargetException {
		BeanInfo bi = Introspector.getBeanInfo(bean.getClass());
		PropertyDescriptor[] pds = bi.getPropertyDescriptors();
		Map<String, Object> ret = new HashMap<String, Object>();
		for (PropertyDescriptor pd : pds) {
			Method m = pd.getReadMethod();
			if (m != null && !"class".equals(pd.getName())) {
				Object v = m.invoke(bean);
				ret.put(pd.getName(), v);
			}
		}
		return ret;
	}

	public static String ensureLength(String input, int maxLen) {
		if (input == null) {
			return null;
		} else if (input.length() > maxLen) {
			return input.substring(0, maxLen);
		} else {
			return input;
		}
	}
}
